import argparse
from moviepy.editor import VideoFileClip
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import threading
import time
import tkinter.font as tkfont
import sys
import logging
import traceback

# 添加在类定义之前
logging.basicConfig(
    filename=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conversion.log'),
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

def handle_exception(exc_type, exc_value, exc_traceback):
    error_msg = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
    logging.critical("未捕获异常:\n%s", error_msg)
    tk.messagebox.showerror("致命错误", f"程序发生未预期错误:\n{exc_value}")

class VideoConverterApp:
    def __init__(self):
        sys.excepthook = handle_exception
        self.root = tk.Tk()
        self.root.title("视频转音频转换器    by: Rectifying")
        
        # 初始化样式
        self.style = ttk.Style()
        self.create_widgets()
        self.conversion_thread = None
        
        # 初始化进度控制变量
        self.simulating_progress = False
        self.actual_conversion_done = False
        self.simulation_thread = None

    def create_widgets(self):
        # 统一布局参数
        pad_x = 10
        pad_y = 8
        
        # 输入文件选择
        ttk.Label(self.root, text="输入视频路径：", style='TLabel').grid(row=0, column=0, padx=pad_x, pady=pad_y, sticky='w')
        self.input_entry = ttk.Entry(self.root, width=38)
        self.input_entry.grid(row=0, column=1, padx=pad_x, pady=pad_y, sticky='ew')
        ttk.Button(self.root, text="浏览文件", command=self.select_input_file, style='Round.TButton').grid(row=0, column=2, padx=pad_x, pady=pad_y)

        # 输出格式选择
        ttk.Label(self.root, text="输出格式:", style='TLabel').grid(row=1, column=0, padx=pad_x, pady=pad_y, sticky='w')
        self.format_combo = ttk.Combobox(self.root, values=['MP3', 'AAC', 'WAV', 'FLAC'], state='readonly')
        self.format_combo.set('MP3')
        self.format_combo.grid(row=1, column=1, padx=pad_x, pady=pad_y, sticky='ew')
        self.format_combo.bind('<<ComboboxSelected>>', self.update_output_extension)

        # 输出路径选择
        ttk.Label(self.root, text="保存路径:", style='TLabel').grid(row=2, column=0, padx=pad_x, pady=pad_y, sticky='w')
        self.output_entry = ttk.Entry(self.root, width=38)
        self.output_entry.grid(row=2, column=1, padx=pad_x, pady=pad_y, sticky='ew')
        ttk.Button(self.root, text="浏览...", command=self.select_output_file, style='Round.TButton').grid(row=2, column=2, padx=pad_x, pady=pad_y)

        # 比特率选择
        ttk.Label(self.root, text="音频质量:", style='TLabel').grid(row=3, column=0, padx=pad_x, pady=pad_y, sticky='w')
        self.bitrate_combo = ttk.Combobox(self.root, values=['128k', '192k', '256k', '320k'], state='readonly')
        self.bitrate_combo.set('192k')
        self.bitrate_combo.grid(row=3, column=1, padx=pad_x, pady=pad_y, sticky='ew')

        # 添加转换按钮
        self.convert_btn = ttk.Button(self.root, 
                                    text="开始转换",
                                    command=self.start_conversion,
                                    style='Round.TButton')
        self.convert_btn.grid(row=5, column=1, pady=15, sticky='ew')

        # 调整标签样式
        self.style.configure('TLabel',
                           background='#F5F5F7',
                           bordercolor='#F5F5F7',
                           font=('微软雅黑', 10),
                           foreground='#1C1C1E')

        # 去除所有输入框边框
        self.style.map('TEntry',
                      fieldbackground=[('readonly','#F5F5F7')],
                      bordercolor=[('!focus','#F5F5F7'),('focus','#007AFF')])

        # 现代控件样式配置
        self.style.theme_use('clam')  # 确保主题先应用
        self.style.configure('Round.TButton', 
                            borderwidth=0,
                            relief="flat",
                            foreground="#FFFFFF",
                            background="#007AFF",
                            padding=8,
                            bordercolor="#007AFF",
                            focusthickness=3,
                            focuscolor="#007AFF",
                            font=('微软雅黑', 10, 'bold'),
                            borderradius=15)

        self.style.configure('Modern.TEntry',
                            fieldbackground="#FFFFFF",
                            bordercolor="#C7C7CC",
                            lightcolor="#E5E5EA",
                            darkcolor="#E5E5EA",
                            padding=(10,8),
                            relief="flat",
                            borderradius=8)

        # 进度条框架
        self.progress_frame = ttk.Frame(self.root)
        self.progress_frame.grid(row=4, column=0, columnspan=3, padx=15, pady=10, sticky="ew")

        # 彩虹进度条实现
        self.progress_canvas = tk.Canvas(self.progress_frame, height=20, bg='#FFFFFF', highlightthickness=0)
        self.progress_canvas.pack(fill='both', expand=True)
        

        
        # 现代风格进度条
        self.style.theme_use('clam')
        
        
        # 进度条样式
        self.style.configure('Custom.Horizontal.TProgressbar',
                            troughcolor='#E5E5EA',
                            background='#34C759',
                            thickness=20,
                            relief='flat',
                            bordercolor='#E5E5EA')
        

        
        # 进度百分比标签
        self.progress_label = ttk.Label(self.progress_frame, 
                                       text="0%", 
                                       font=('微软雅黑', 10), 
                                       foreground='#666')
        self.progress_label.pack(side=tk.RIGHT, padx=10)



    def select_input_file(self):
        file_path = filedialog.askopenfilename(filetypes=[("视频文件", "*.mp4 *.avi *.mov *.mkv *.flv *.wmv *.webm")])
        if file_path:
            self.input_entry.delete(0, tk.END)
            self.input_entry.insert(0, file_path)
            
            if not self.output_entry.get():
                base_name = os.path.splitext(os.path.basename(file_path))[0]
                output_dir = os.path.dirname(file_path)
                current_format = self.format_combo.get().lower()
            output_path = os.path.join(output_dir, f"{base_name}.{current_format}")
            self.output_entry.insert(0, output_path)

    def update_output_extension(self, event=None):
        if self.input_entry.get() and self.output_entry.get():
            base_path = os.path.splitext(self.output_entry.get())[0]
            new_extension = self.format_combo.get().lower()
            new_path = f"{base_path}.{new_extension}"
            self.output_entry.delete(0, tk.END)
            self.output_entry.insert(0, new_path)

    def select_output_file(self):
        current_format = self.format_combo.get().lower()
        file_path = filedialog.asksaveasfilename(
            defaultextension=f".{current_format}",
            filetypes=[(f"{self.format_combo.get()} 文件", f"*.{current_format}")]
        )
        if file_path:
            self.output_entry.delete(0, tk.END)
            self.output_entry.insert(0, file_path)

    def convert_video_to_audio(self, input_path, output_path=None, bitrate='192k', format_type='MP3'):
        try:
            if not os.path.exists(input_path):
                raise FileNotFoundError(f"输入文件 {input_path} 不存在")
                
            video = VideoFileClip(input_path)
            audio = video.audio
            
            if output_path is None:
                base_name = os.path.splitext(os.path.basename(input_path))[0]
                output_path = f"{base_name}.{format_type.lower()}"

            codec_map = {
                'MP3': {'codec': 'libmp3lame', 'params': []},
                'AAC': {'codec': 'aac', 'params': ['-strict', 'experimental']},
                'WAV': {'codec': 'pcm_s16le', 'params': ['-ar', '44100', '-ac', '2']},
                'FLAC': {'codec': 'flac', 'params': ['-compression_level', '12']}
            }
            selected_format = format_type
            
            total_duration = video.duration
            
            def progress_callback(progress):
                self.root.after(0, lambda: self.update_progress(progress * 100))

            audio.write_audiofile(
                output_path,
                bitrate=bitrate if selected_format not in ['WAV', 'FLAC'] else None,
                codec=codec_map[selected_format]['codec'],
                ffmpeg_params=codec_map[selected_format]['params'],
                logger='bar'
                
            
            )
            print(f"转换成功！输出文件: {output_path}")
            return True
        
        except Exception as e:
            print(f"转换失败: {str(e)}")
            return False
        finally:
            if 'video' in locals():
                video.close()


    
    def start_conversion(self):
        if self.conversion_thread and self.conversion_thread.is_alive():
            return
    
        # 创建模拟进度线程
        def simulate_progress():
            import random
            current_progress = 0
            while (current_progress < 100 or not self.actual_conversion_done) and self.simulating_progress:
                # 优先检测实际转换是否完成
                if self.actual_conversion_done and current_progress < 95:
                    current_progress = 95  # 快速跳转到最终阶段
                
                increment = random.randint(1, 5) if not self.actual_conversion_done else 5
                current_progress = min(current_progress + increment, 100)
                self.root.after(0, lambda v=current_progress: self.update_progress(v))
                time.sleep(random.uniform(0.1, 0.3) if not self.actual_conversion_done else 0.05)
            
            # 等待实际转换完成
            # 等待实际转换完成并同步最终进度
            while not self.actual_conversion_done:
                time.sleep(0.1)
            
            # 确保最终进度显示100%
            self.root.after(0, lambda: self.update_progress(100))
            self.root.after(100, lambda: [self.on_conversion_done(True), setattr(self, 'simulating_progress', False)])
    
        # 启动实际转换线程
        def conversion_wrapper():
            try:
                result = self.convert_video_to_audio(input_path, output_path, bitrate, format_type)
                self.actual_conversion_done = True
            except Exception as e:
                import traceback
                error_msg = f"{str(e)}\n\n完整堆栈追踪:\n{traceback.format_exc()}"
                self.root.after(0, lambda: self.on_conversion_done(False, error_msg))
                self.simulating_progress = False
                self.root.after(0, lambda: [self.progress_label.config(text='0%'), self.draw_progress(0)])

        self.simulating_progress = True
        self.actual_conversion_done = False
        self.convert_btn.config(state=tk.DISABLED)
        self.draw_progress(0)
        
        input_path = self.input_entry.get()
        output_path = self.output_entry.get()
        bitrate = self.bitrate_combo.get()
        format_type = self.format_combo.get()

        # 启动线程
        self.conversion_thread = threading.Thread(target=conversion_wrapper)
        self.simulation_thread = threading.Thread(target=simulate_progress)
        self.conversion_thread.start()
        self.simulation_thread.start()

    def update_progress(self, value):
        if self.simulating_progress:
            self.draw_progress(value)
        
    def draw_progress(self, value):
        self.progress_canvas.delete('all')
        width = self.progress_canvas.winfo_width()
        progress_width = width * (value / 100)
        
        colors = ['#FF3B30', '#FF9500', '#FFCC00', '#34C759', '#5AC8FA', '#007AFF', '#5856D6']
        color_stops = len(colors)
        segment_width = progress_width / color_stops
        
        for i in range(color_stops):
            x0 = i * segment_width
            x1 = (i+1) * segment_width
            self.progress_canvas.create_rectangle(
                x0, 0, x1, 20,
                fill=colors[i],
                outline='',
                width=0
            )
        self.progress_label.config(text=f'{int(value)}%')

    def on_conversion_done(self, success, error_msg=None):
        self.convert_btn.config(state=tk.NORMAL)
        self.update_progress(100)
        if success:
            messagebox.showinfo("成功", "转换完成！")
        else:
            messagebox.showerror("错误", error_msg or "转换失败")

    def run(self):
        self.root.geometry('650x350')
        self.root.resizable(False, False)
        self.root.configure(padx=20, pady=20, background='#F5F5F7')
        # 统一字体设置（需要系统安装楷体）
        self.kaiti_font = ('楷体', 11) if '楷体' in tkfont.families() else ('微软雅黑', 11)
        self.style.configure('TButton', font=self.kaiti_font, padding=10, width=12)
        self.style.configure('TLabel',
                           font=('微软雅黑', 10),
                           foreground='#1C1C1E')
        self.style.configure('TEntry', fieldbackground='#FFFFFF', bordercolor='#C7C7CC',
                           padding=5, insertcolor='#007AFF')
        self.style.configure('TCombobox', selectbackground='#007AFF')
                # 添加水印
        watermark = ttk.Label(self.root, 
                            text='Rectifying',
                            font=('楷体', 9) if '楷体' in tkfont.families() else self.kaiti_font,
                            foreground='#FFB6C1',
                            background='#F5F5F7',
                            style='TLabel')
        watermark.place(relx=1.0, rely=1.0, anchor='se')

        self.root.mainloop()

def main():
    try:
        parser = argparse.ArgumentParser(description='视频转MP3转换器')
        parser.add_argument('input', nargs='?', help='输入视频文件路径')
        parser.add_argument('-o', '--output', help='输出MP3文件路径（可选）')
        parser.add_argument('-b', '--bitrate', default='192k', help='音频比特率（默认：192k）')
        parser.add_argument('--gui', action='store_true', help='启动图形界面')
        
        args = parser.parse_args()
        
        if args.gui or not args.input:
            app = VideoConverterApp()
            app.run()
    except Exception as e:
        import traceback
        with open('error.log', 'a') as f:
            f.write(f'崩溃日志: {str(e)}\n{traceback.format_exc()}')
        messagebox.showerror('致命错误', f'程序异常崩溃:\n{str(e)}')

if __name__ == '__main__':
    main()